/*
    Copyright (C) 2010, 2011, 2012 Daniel Gutson, Martin Gioiosa,
    Lucas Paradisi, Leonardo Boquillon and Hugo Arregui, FuDePAN

    This file is part of Biopp.

    Aso is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Biopp is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Biopp.  If not, see <http://www.gnu.org/licenses/>.

    NOTE: This file is in prototype stage, and is under active development.
*/

#ifndef BIO_TRIPLETS_H
#define BIO_TRIPLETS_H

#include <map>
#include <mili/mili.h>
#include <stl-debug/stl_debug.h>
#include "biopp/bio_molecular/exceptions.h"
#include "biopp/bio_molecular/definitions.h"

using std::string;
using mili::insert_into;

namespace biopp
{

inline bool isPureNucleotide(const PseudoNucleotide& n)
{
    return n.value < PseudoNucleotide::R;
}

inline void initialize_distance_matrix(DistanceMatrix& matrix)
{
    for (size_t i = 0; i < MATRIX_SIZE; ++i)
    {
        for (size_t j = 0; j < MATRIX_SIZE; ++j)
            matrix[i][j] = i == j ? NucDistance(0) : NucDistance(1);
    }
}

template<typename NucleotideClass>
class TripletBase
{
protected:
    struct
    {
        NucleotideClass n[3];

        NucleotideClass operator[](size_t i) const
        {
            return n[i];
        }

        NucleotideClass& operator[](size_t i)
        {
            return n[i];
        }

    } triplet;

public:
    typedef NucleotideClass NucType;

    TripletBase()
    {}

    TripletBase(const NucleotideClass& n1, const NucleotideClass& n2, const NucleotideClass& n3)
    {
        triplet[0] = n1;
        triplet[1] = n2;
        triplet[2] = n3;
    }

    TripletBase(const TripletBase& other)
        : triplet(other.triplet)
    {}

    inline TripletBase& operator=(const TripletBase& other);

    inline bool operator==(const TripletBase& other) const;

    inline bool operator<(const TripletBase& other) const;

    inline NucleotideClass operator[](size_t index) const;

    inline NucleotideClass& operator[](size_t index);

    inline bool contains(NucleotideClass n) const;

    inline size_t distance(const TripletBase<NucleotideClass>& other) const;

    inline string to_string() const;
};

class Triplet : public TripletBase<Nucleotide>
{
public:
    Triplet()
        : TripletBase<Nucleotide>()
    {
        triplet[0] = 'A';
        triplet[1] = 'A';
        triplet[2] = 'A';
    }

    Triplet(const std::string& s) throw(InvalidNucleotide)
        : TripletBase<Nucleotide>()
    {
        from_string(s);
    }

    Triplet(const char* s) throw(InvalidNucleotide)
        : TripletBase<Nucleotide>()
    {
        from_string(s);
    }

    Triplet(const Triplet& other)
        : TripletBase<Nucleotide>(other)
    {}

    Triplet(const Nucleotide& n1, const Nucleotide& n2, const Nucleotide& n3)
        : TripletBase<Nucleotide>(n1, n2, n3)
    {}

    Triplet& operator=(const Triplet& other)
    {
        TripletBase<Nucleotide>::operator =(other);
        return *this;
    }

    NucDistance distance(const Triplet& other, const DistanceMatrix& matrix) const
    {
        const Nucleotide t0 = triplet[0];
        const Nucleotide t1 = triplet[1];
        const Nucleotide t2 = triplet[2];

        return matrix[t0.value][other.triplet[0].value] +
               matrix[t1.value][other.triplet[1].value] +
               matrix[t2.value][other.triplet[2].value];
    }

    size_t min_distance(Aminoacid aminoacid) const throw(InvalidAminoacid);

    inline Aminoacid translate() const;

    inline bool isSibling(const Triplet& other) const;

    TripletsIterator getSiblingTriplets() const;
    TripletsIterator mutate_single(Nucleotide from, Nucleotide to) const;
    //TripletsIterator mutate_all(Nucleotide from, Nucleotide to) const;   Next Version

    void complement()
    {
        triplet[0].complement();
        triplet[1].complement();
        triplet[2].complement();
    }

    void getComplement(Triplet& t) const
    {
        t = *this;
        t.complement();
    }

    inline bool isStop() const;

    static Nucleotide convertNucTypeFromChar(char c)
    {
        return Nucleotide(c);
    }

private:
    void from_string(const std::string& s) throw(InvalidNucleotide);
};

class PseudoTriplet : public TripletBase<PseudoNucleotide>
{
public:

    PseudoTriplet()
        : TripletBase<PseudoNucleotide>()
    {
        triplet[0] = 'A';
        triplet[1] = 'A';
        triplet[2] = 'A';
    }

    PseudoTriplet(const std::string& s) throw(InvalidNucleotide)
        : TripletBase<PseudoNucleotide>()
    {
        from_string(s);
    }

    PseudoTriplet(const char* s) throw(InvalidNucleotide)
        : TripletBase<PseudoNucleotide>()
    {
        from_string(s);
    }

    PseudoTriplet(const PseudoTriplet& other)
        : TripletBase<PseudoNucleotide>(other)
    {}

    PseudoTriplet(const PseudoNucleotide& pn1, const PseudoNucleotide& pn2, const PseudoNucleotide& pn3)
        : TripletBase<PseudoNucleotide>(pn1, pn2, pn3)
    {}

    bool isPureTriplet() const;

    TripletsIterator getPureTriplets() const;

    PseudoTriplet& operator=(const PseudoTriplet& other)
    {
        TripletBase<PseudoNucleotide>::operator=(other);
        return *this;
    }

    static PseudoNucleotide convertNucTypeFromChar(char c)
    {
        return PseudoNucleotide(c);
    }

private:
    void from_string(const std::string& s) throw(InvalidNucleotide);
};

class GeneticCode
{
STL_DEBUG_PRIVATE:
    std::map<Triplet, Aminoacid> t2a;
    std::multimap<Aminoacid, Triplet> a2t;
    std::set<Aminoacid> aminoacids;

private:
    GeneticCode();
    static GeneticCode instance;

    void add(const Triplet& t, Aminoacid aa)
    {
        Triplet tri(t);
        t2a[tri] = aa;
        a2t.insert(std::pair<Aminoacid, Triplet>(aa, tri));
        aminoacids.insert(aa);
    }

    void add(const Triplet& t, const char* aminoacid)
    {
        add(t, Aminoacid(aminoacid[0]));
    }

public:
    static Aminoacid translate(const Triplet& t)
    {
        return Aminoacid(instance.t2a.find(t)->second);
    }

    static TripletsIterator triplets(Aminoacid amino) throw(InvalidAminoacid);

    static TripletsIterator allTriplets();

    static AminoacidsIterator allAminoacids();
};

class ExtendedGeneticCode
{
    PseudoNucleotide a[2], c[2], g[2], t[2],
                     r[3], y[3], k[3], m[3], s[3], w[3],
                     b[4], d[4], h[4], v[4],
                     n[5];

    const PseudoNucleotide* expansions[15];

private:
    ExtendedGeneticCode();
    static ExtendedGeneticCode instance;

public:
    static TripletsIterator possibleTriplets(const PseudoTriplet& pseudo);
};
}

#define BIO_TRIPLETS_INLINE_H
#include "triplets_inline.h"
#undef BIO_TRIPLETS_INLINE_H

#endif
